package com.distributed.loginserver;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.SocketAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import com.distributed.common.Address;
import com.distributed.common.Constant;
import com.distributed.common.EventHandler;
import com.distributed.common.message.BaseMessage;
import com.distributed.common.message.ErrorMessage;
import com.distributed.common.message.LogicServInfoMessage;
import com.distributed.common.message.LoginMessage;
import com.distributed.common.message.LoginServInfoMessage;
import com.distributed.common.message.LogoutMessage;
import com.distributed.common.message.LogoutRespMessage;
import com.distributed.util.HashUtils;

public class LoginServer {
	private final static int LEASE = 3600 * 24;
	static {
		PropertyConfigurator.configure("log4j.properties");
	}
	private static Logger logger = Logger.getLogger(LoginServer.class);
	
	private static int port = 2234;
	private static List<Address> logic_address_list = null;
	private static List<Address> data_address_list = null;
	private static List<Address> cache_address_list = null;
	private static List<JedisPool> jedisCachePoolList = new ArrayList<JedisPool>();
	private static List<JedisPool> jedisDataPoolList = new ArrayList<JedisPool>();
	
	private static void initServer() {
		LoginConfiguration.load();
		String hostIp = LoginConfiguration.getHostIp();
		int mport = LoginConfiguration.getHostPort();
		if (mport != -1) {
			port = mport;
		}
		if (hostIp == null) {
			try {
				hostIp = InetAddress.getLocalHost().getHostAddress();
			} catch (UnknownHostException e) {
				e.printStackTrace();
			}
			logger.info("Host: " + hostIp + ":" + port);
		} else {
			logger.info("Host: " + hostIp + ":" + port);
		}

		logger.info("logic server list:");
		logic_address_list = LoginConfiguration.getLogicServerList();
		if (logic_address_list == null || logic_address_list.size() == 0) {
			logger.warn("Address list is empty");
			return;
		}
		for (Address address : logic_address_list) {
			logger.info(address.toString());
		}
		
		logger.info("data server list:");
		data_address_list = LoginConfiguration.getLogicServerList();
		if (data_address_list == null || data_address_list.size() == 0) {
			logger.warn("Address list is empty");
			return;
		}
		for (Address address : data_address_list) {
			logger.info(address.toString());
		}
		
		logger.info("cache server list:");
		cache_address_list = LoginConfiguration.getLogicServerList();
		if (cache_address_list == null || cache_address_list.size() == 0) {
			logger.warn("Address list is empty");
			return;
		}
		for (Address address : cache_address_list) {
			logger.info(address.toString());
		}
	}

	private static void startServer() throws Exception {
		for(int i = 0; i < data_address_list.size(); i++) {
			JedisPool jedisPool = new JedisPool(data_address_list.get(i).getIp(),
					data_address_list.get(i).getPort());
			jedisDataPoolList.add(jedisPool);
		}
		for(int i = 0; i < cache_address_list.size(); i++) {
			JedisPool jedisPool = new JedisPool(cache_address_list.get(i).getIp(),
					cache_address_list.get(i).getPort());
			jedisCachePoolList.add(jedisPool);
		}
		
		SocketAcceptor acceptor = new NioSocketAcceptor();
		
		DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();
		chain.addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory()));
		chain.addLast("logger", new LoggingFilter());
		
		LoginProtocolHandler loginProtocolHandler = new LoginProtocolHandler();
		loginProtocolHandler.setHandler(loginEventHandler);
		acceptor.setHandler(loginProtocolHandler);
		acceptor.setReuseAddress(true);
		acceptor.bind(new InetSocketAddress(port));

		logger.info("Listening on Port: " + port);

		while (true) {
			logger.info("R: "
					+ acceptor.getStatistics().getReadBytesThroughput()
					+ " W: "
					+ acceptor.getStatistics().getWrittenBytesThroughput());
			Thread.sleep(60000);
		}
	}
	
	public static void main(String[] args) throws Exception {
		logger.info("login server");
		
		initServer();
		startServer();
	}
	
	private static EventHandler loginEventHandler = new EventHandler() {

		public String process(IoSession ioSession, String request) {
			BaseMessage baseMessage = BaseMessage.getFromString(request);
			switch (baseMessage.getId()) {
			case Constant.LOGIN_MESSAGE:
				return login(LoginMessage.getFromString(request));
			case Constant.LOGOUT_MESSAGE:
				return logout(LogoutMessage.getFromString(request));
			default:
				return ErrorMessage.ERROR;
			}
		}

		public void closeSession(IoSession ioSession) {

		}
	};
	
	private static String login(LoginMessage loginMessage) {
		if (loginMessage == null) {
			return LogicServInfoMessage.ERROR;
		}
		
		String username = loginMessage.getUserName();
		if (username == null || "".equals(username)) {
			LoginServInfoMessage loginServInfoMessage = new LoginServInfoMessage();
			loginServInfoMessage.setMsg("error:user not exist");
			return loginServInfoMessage.toString();
		}
		
		JedisPool jedisDataPool = jedisDataPoolList.get(HashUtils.ModHash(jedisDataPoolList.size(),
				username));
		Jedis dataJedis = jedisDataPool.getResource();
		Map<String, String> person = dataJedis.hgetAll(Constant.DATA_USERNAME);
		jedisDataPool.returnResource(dataJedis);
		if(person == null || person.size() == 0) {
			LoginServInfoMessage loginServInfoMessage = new LoginServInfoMessage();
			loginServInfoMessage.setMsg("error:user not exist");
			return loginServInfoMessage.toString();
		}
		
		String password = person.get(Constant.DATA_PASSWORD);
		if (password == null || "".equals(password)) {
			LoginServInfoMessage loginServInfoMessage = new LoginServInfoMessage();
			loginServInfoMessage.setMsg("error:user not exist");
			return loginServInfoMessage.toString();
		}
		
		if (!password.equals(loginMessage.getPassword())) {
			LoginServInfoMessage loginServInfoMessage = new LoginServInfoMessage();
			loginServInfoMessage.setMsg("error:password error");
			return loginServInfoMessage.toString();
		}
		
		person.put(Constant.DATA_ONLINE, Constant.DATA_ON);
		person.put(Constant.DATA_IP, loginMessage.getIp());
		person.put(Constant.DATA_PORT, loginMessage.getPort());
		person.put(Constant.DATA_TIMESTAMP, String.valueOf(System.currentTimeMillis()));
		person.put(Constant.DATA_VERSION, loginMessage.getVersion());
		
		JedisPool jedisPool = jedisCachePoolList.get(HashUtils.ModHash(jedisDataPoolList.size(),
				username));
		Jedis cacheJedis = jedisPool.getResource();
		boolean isSuccess = true;
		try {
			cacheJedis.hmset(username, person);
			cacheJedis.expire(username, LEASE);
		} catch(Exception e) {
			e.printStackTrace();
			isSuccess = false;
		}
		jedisPool.returnResource(cacheJedis);
		
		if(isSuccess == false) {
			LoginServInfoMessage loginServInfoMessage = new LoginServInfoMessage();
			loginServInfoMessage.setMsg("error:login failed");
			return loginServInfoMessage.toString();
		}
		
		if (logic_address_list == null || logic_address_list.size() == 0) {
			LoginServInfoMessage loginServInfoMessage = new LoginServInfoMessage();
			loginServInfoMessage.setMsg("error:no logic server can be used");
			return loginServInfoMessage.toString();
		}
		
		Address address = HashUtils.HashRandom(logic_address_list);
		
		return new LoginServInfoMessage(address.getIp(), address.getPort(), LEASE).toString();
	}
	
	private static String logout(LogoutMessage logoutMessage) {
		if (logoutMessage == null) {
			return LogoutRespMessage.ERROR;
		}
		
		String username = logoutMessage.getUserName();
		if (username == null || "".equals(username)) {
			LogoutRespMessage logoutRespMessage = new LogoutRespMessage();
			logoutRespMessage.setMsg("error:user not exist");
			return logoutRespMessage.toString();
		}
		
		JedisPool jedisPool = jedisCachePoolList.get(HashUtils.ModHash(jedisDataPoolList.size(),
				username));
		Jedis cacheJedis = jedisPool.getResource();
		cacheJedis.del(username);
		jedisPool.returnResource(cacheJedis);
		
		return new LogoutRespMessage(Constant.SUCCESS).toString();
	}
}
